package com.dragon.modules.yuegou.web;

import com.dragon.common.exception.DragonException;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.IDN;
import java.util.Collection;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.util.regex.Pattern.CASE_INSENSITIVE;

/**
 * 验证和断言工具类。
 * 
 * @author liuwu
 *
 */
public final class Check {
	
	private Check() {
		
	}

	private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator();

	/**
	 * 验证对象不空
	 * 
	 * @param obj
	 *            被验证的对象
	 * @param errorMessageTemplate
	 *            验证失败是提示的消息（模板）
	 * @param errorMessageArgs
	 *            消息模板的参数，可选
	 * @throws DragonException
	 *             如果验证不通过
	 */
	public static void notNull(Object obj, String errorMessageTemplate, Object... errorMessageArgs) {
		if (obj == null) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}

	// XXX {@code float} {@code double} 是否应该考虑在内？
	/**
	 * 验证数字，整数位少于{@code maxIntegerLength}位，小数位少于{@code maxFractionLength}位。<br>
	 * 特别的，当{@code number}为小数时，如果整数位是0（例如0.12），整数位长度算作0。 支持的类型：
	 * <ul>
	 * <li>{@code BigDecimal}</li>
	 * <li>{@code BigInteger}</li>
	 * <li>{@code byte}, {@code short}, {@code int}, {@code long}, 以及它们的封装类型</li>
	 * </ul>
	 * 
	 * {@code number}为{@code null}会通过验证。
	 * 
	 * @param number
	 *            被验证的数字
	 * @param maxIntegerLength
	 *            整数位最大长度，包含，不能是负数
	 * @param maxFractionLength
	 *            小数位最大长度，包含，不能是负数
	 * @param errorMessageTemplate
	 *            验证失败是提示的消息（模板）
	 * @param errorMessageArgs
	 *            消息模板的参数，可选
	 * @throws DragonException
	 *             如果验证不通过
	 */
	public static void digits(Number number, int maxIntegerLength, int maxFractionLength, String errorMessageTemplate,
			Object... errorMessageArgs) {
		if (!isValidDigits(number, maxIntegerLength, maxFractionLength)) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}

	/**
	 * 验证数字的范围，在{@code minInclusive}和{@code maxInclusive}之间。 <br>
	 * 支持的类型：
	 * <ul>
	 * <li>{@code BigDecimal}</li>
	 * <li>{@code BigInteger}</li>
	 * <li>{@code byte}, {@code short}, {@code int}, {@code long},
	 * {@code float},{@code double}, 以及它们的封装类型</li>
	 * </ul>
	 * 
	 * @param number
	 *            被验证的数字
	 * @param minValueInclusive
	 *            最小值（包含）
	 * @param maxValueInclusive
	 *            最大值（包含）
	 * @param errorMessageTemplate
	 *            验证失败是提示的消息（模板）
	 * @param errorMessageArgs
	 *            消息模板的参数，可选
	 * @throws DragonException
	 *             如果验证不通过
	 */
	public static void range(Number number, long minValueInclusive, long maxValueInclusive, String errorMessageTemplate,
			Object... errorMessageArgs) {
		if (!isInRange(number, minValueInclusive, maxValueInclusive)) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}

	private static boolean isInRange(Number number, long minValueInclusive, long maxValueInclusive) {
		// null values are valid
		if (number == null) {
			return true;
		} else if (number instanceof Integer) {
			double intValue = (Integer) number;
			return intValue >= minValueInclusive && intValue <= maxValueInclusive;
		} else if (number instanceof Double) {
			return isDouble(number,minValueInclusive,maxValueInclusive);
		} else if (number instanceof Float) {
			return isFloat(number,minValueInclusive,maxValueInclusive);
		} else if (number instanceof BigDecimal) {
			return isBigDecimal(number,minValueInclusive,maxValueInclusive);
		} else if (number instanceof BigInteger) {
			return isBigInteger(number,minValueInclusive,maxValueInclusive);
		} else {
			long longValue = number.longValue();
			return longValue >= minValueInclusive && longValue <= maxValueInclusive;
		}
	}

	private static boolean isDouble(Number number, long minValueInclusive, long maxValueInclusive){
		double doubleValue = (Double) number;
		if (Double.isNaN(doubleValue) || doubleValue == Double.NEGATIVE_INFINITY
				|| doubleValue == Double.POSITIVE_INFINITY) {
			return false;
		} else {
			return doubleValue >= minValueInclusive && doubleValue <= maxValueInclusive;
		}
	}

	private static boolean isFloat(Number number, long minValueInclusive, long maxValueInclusive){
		float floatValue = (Float) number;
		if (Float.isNaN(floatValue) || floatValue == Float.NEGATIVE_INFINITY
				|| floatValue == Float.POSITIVE_INFINITY) {
			return false;
		} else {
			return floatValue >= minValueInclusive && floatValue <= maxValueInclusive;
		}
	}

	private static boolean isBigDecimal(Number number, long minValueInclusive, long maxValueInclusive){
		BigDecimal bigValue = (BigDecimal) number;
		return bigValue.compareTo(BigDecimal.valueOf(minValueInclusive)) >= 0
				&& bigValue.compareTo(BigDecimal.valueOf(maxValueInclusive)) <= 0;
	}

	private static boolean isBigInteger(Number number, long minValueInclusive, long maxValueInclusive){
		BigInteger bigValue = (BigInteger) number;
		return bigValue.compareTo(BigInteger.valueOf(minValueInclusive)) >= 0
				&& bigValue.compareTo(BigInteger.valueOf(maxValueInclusive)) <= 0;
	}

	/**
	 * 验证数字，整数位少于{@code maxIntegerLength}位，小数位少于{@code maxFractionLength}位<br>
	 * 特别的，1.当{@code number}为小数时，如果整数位是0（例如0.12），整数位长度算作0；2.如果被验证的字符串不是数字验证不通过；
	 * 3.小数位即使是0也计算在内，例如0.00的小数位长度是2。
	 * 
	 * {@code number}为{@code null}会通过验证。
	 * 
	 * @param number
	 *            被验证的数字
	 * @param integer
	 *            整数位最大长度，包含
	 * @param fraction
	 *            小数位最大长度，包含
	 * @param errorMessageTemplate
	 *            验证失败是提示的消息（模板）
	 * @param errorMessageArgs
	 *            消息模板的参数，可选
	 * @throws DragonException
	 *             如果验证不通过
	 */
	public static void digits(CharSequence number, int integer, int fraction, String errorMessageTemplate,
			Object... errorMessageArgs) {
		nonNegative(integer, "integer must non negative, integer : %s", integer);
		nonNegative(fraction, "fraction must non negative, fraction : %s", fraction);
		if (!isValidDigits(number, integer, fraction)) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}

	/**
	 * 验证数字非负。 支持的类型：
	 * <ul>
	 * <li>{@code BigDecimal}</li>
	 * <li>{@code BigInteger}</li>
	 * <li>{@code byte}, {@code short}, {@code int}, {@code long},
	 * {@code float},{@code double}, 以及它们的封装类型</li>
	 * </ul>
	 * 
	 * {@code number}为{@code null}会通过验证。
	 * 
	 * {@code NaN}通不过验证。
	 * 
	 * @param number
	 *            被验证的数字
	 * @param errorMessageTemplate
	 *            验证失败是提示的消息（模板）
	 * @param errorMessageArgs
	 *            消息模板的参数，可选
	 * @throws DragonException
	 *             如果验证不通过
	 */
	public static void nonNegative(Number number, String errorMessageTemplate, Object... errorMessageArgs) {
		if (!isNonnegative(number)) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}

	/**
	 * 验证数字是正数。 支持的类型：
	 * <ul>
	 * <li>{@code BigDecimal}</li>
	 * <li>{@code BigInteger}</li>
	 * <li>{@code byte}, {@code short}, {@code int}, {@code long},
	 * {@code float},{@code double}, 以及它们的封装类型</li>
	 * </ul>
	 * 
	 * {@code number}为{@code null}会通过验证。
	 * 
	 * {@code NaN}通不过验证。
	 * 
	 * @param number
	 *            被验证的数字
	 * @param errorMessageTemplate
	 *            验证失败是提示的消息（模板）
	 * @param errorMessageArgs
	 *            消息模板的参数，可选
	 * @throws DragonException
	 *             如果验证不通过
	 */
	public static void positive(Number number, String errorMessageTemplate, Object... errorMessageArgs) {
		if (!isPositive(number)) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}

	/**
	 * 状态验证。验证传入的{@code state}为true。
	 * 
	 * @param expression
	 *            状态表达式
	 * @param errorMessageTemplate
	 *            验证失败是提示的消息（模板）
	 * @param errorMessageArgs
	 *            消息模板的参数，可选
	 * @throws IllegalStateException
	 *             如果验证不通过
	 */
	public static void state(boolean expression, String errorMessageTemplate, Object... errorMessageArgs) {
		if (!expression) {
			throw new IllegalStateException(format(errorMessageTemplate, errorMessageArgs));
		}
	}

	/**
	 * 验证数组没有{@code null}元素。<br>
	 * 
	 * {@code array}为{@code null}会通过验证。
	 * 
	 * {@code array}长度为0会通过验证。
	 * 
	 * @param array
	 *            被验证的数组
	 * @param errorMessageTemplate
	 *            验证失败是提示的消息（模板）
	 * @param errorMessageArgs
	 *            消息模板的参数，可选
	 * @throws DragonException
	 *             如果验证不通过
	 */
	public static void noNullElements(Object[] array, String errorMessageTemplate, Object... errorMessageArgs) {
		if (array == null) {
			return;
		}
		for (Object element : array) {
			if (element == null) {
				throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
			}
		}
	}

	/**
	 * 验证字符串不是blank字符串。<br>
	 * blank字符串：{@code null}, 长度为0或由空白字符组成的字符串<br>
	 * 
	 * @param string
	 *            被验证的字符串
	 * @param errorMessageTemplate
	 *            验证失败是提示的消息（模板）
	 * @param errorMessageArgs
	 *            消息模板的参数，可选
	 * @throws DragonException
	 *             如果验证不通过
	 * 
	 * @see Character#isWhitespace(char) 空白字符判断
	 */
	public static void notBlank(CharSequence string, String errorMessageTemplate, Object... errorMessageArgs) {
		if (!StringUtils.hasText(string)) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}

	/**
	 * 验证字符串不是empty字符串<br>
	 * empty字符串：{@code null}或长度为0的字符串<br>
	 *
	 * @param string
	 *            被验证的字符串
	 * @param errorMessageTemplate
	 *            验证失败是提示的消息（模板）
	 * @param errorMessageArgs
	 *            消息模板的参数，可选
	 * @throws DragonException
	 *             如果验证不通过
	 */
	public static void notEmpty(CharSequence string, String errorMessageTemplate, Object... errorMessageArgs) {
		if (string == null || string.length() == 0) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}

	/**
	 * 判断集合不空。<br>
	 * 空集合：{@code null}或{@code size}为0的集合<br>
	 * 
	 * @param collection
	 *            被验证的集合
	 * @param errorMessageTemplate
	 *            验证失败是提示的消息（模板）
	 * @param errorMessageArgs
	 *            消息模板的参数，可选
	 * @throws DragonException
	 *             如果验证不通过
	 */
	public static void notEmpty(Collection<?> collection, String errorMessageTemplate, Object... errorMessageArgs) {
		if (collection == null || collection.isEmpty()) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}

	/**
	 * 判断Map不空。<br>
	 * 空Map：{@code null}或{@code size}为0的Map<br>
	 * 
	 * @param map
	 *            被验证的Map
	 * @param errorMessageTemplate
	 *            验证失败是提示的消息（模板）
	 * @param errorMessageArgs
	 *            消息模板的参数，可选
	 * @throws DragonException
	 *             如果验证不通过
	 */
	public static void notEmpty(Map<?, ?> map, String errorMessageTemplate, Object... errorMessageArgs) {
		if (map == null || map.isEmpty()) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}

	/**
	 * 判断数组不空。<br>
	 * 空数组：{@code null}或长度为0的数组<br>
	 * 
	 * @param array
	 *            被验证的数组
	 * @param errorMessageTemplate
	 *            验证失败是提示的消息（模板）
	 * @param errorMessageArgs
	 *            消息模板的参数，可选
	 * @throws DragonException
	 *             如果验证不通过
	 */
	public static void notEmpty(Object[] array, String errorMessageTemplate, Object... errorMessageArgs) {
		if (array == null || array.length == 0) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}

	public static void isInstanceOf(Class<?> type, Object obj, String errorMessageTemplate,
			Object... errorMessageArgs) {
		notNull(type, "Type to check against must not be null");
		if (!type.isInstance(obj)) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}

	/**
	 * 验证邮箱地址格式。<br>
	 * 
	 * {@code email}为{@code null}会通过验证。
	 * 
	 * @param email
	 *            被验证的邮箱
	 * @param errorMessageTemplate
	 *            验证失败是提示的消息（模板）
	 * @param errorMessageArgs
	 *            消息模板的参数，可选
	 * @throws DragonException
	 *             如果验证不通过
	 */
	public static void email(CharSequence email, String errorMessageTemplate, Object... errorMessageArgs) {
		if (!EMAIL_VALIDATOR.isValid(email)) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}

	/**
	 * 验证字符串的长度范围,等价于{@code size(CharSequence, int, int, String, Object...)}
	 * 
	 * @param string
	 *            被验证的字符串
	 * @param minInclusive
	 *            最小长度（包含）
	 * @param maxInclusive
	 *            最大长度（包含）
	 * @param errorMessageTemplate
	 *            验证失败是提示的消息（模板）
	 * @param errorMessageArgs
	 *            消息模板的参数，可选
	 * @throws DragonException
	 *             如果验证不通过
	 * @see #size(CharSequence, int, int, String, Object...)
	 */
	public static void length(CharSequence string, int minInclusive, int maxInclusive, String errorMessageTemplate,
			Object... errorMessageArgs) {
		int length = string != null ? string.length() : 0;
		if (length < minInclusive || length > maxInclusive) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}
	
	/**
	 * 验证字符串的长度范围,等价于{@code length(CharSequence, int, int, String, Object...)}
	 * 
	 * @param string
	 *            被验证的字符串
	 * @param minInclusive
	 *            最小长度（包含）
	 * @param maxInclusive
	 *            最大长度（包含）
	 * @param errorMessageTemplate
	 *            验证失败是提示的消息（模板）
	 * @param errorMessageArgs
	 *            消息模板的参数，可选
	 * @throws DragonException
	 *             如果验证不通过
	 * @see #length(CharSequence, int, int, String, Object...)
	 */
	public static void size(CharSequence string, int minInclusive, int maxInclusive, String errorMessageTemplate,
			Object... errorMessageArgs) {
		int size = string != null ? string.length() : 0;
		if (size < minInclusive || size > maxInclusive) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}

	/**
	 * 验证集合元素个数的范围。
	 * 
	 * @param collection
	 *            被验证的集合
	 * @param minInclusive
	 *            最少元素个数（包含）
	 * @param maxInclusive
	 *            最多元素个数（包含）
	 * @param errorMessageTemplate
	 *            验证失败是提示的消息（模板）
	 * @param errorMessageArgs
	 *            消息模板的参数，可选
	 * @throws DragonException
	 *             如果验证不通过
	 */
	public static void size(Collection<?> collection, int minInclusive, int maxInclusive, String errorMessageTemplate,
			Object... errorMessageArgs) {
		if (collection == null) {
			return;
		}
		if (collection.size() < minInclusive || collection.size() > maxInclusive) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}

	public static void size(Map<?, ?> map, int minInclusive, int maxInclusive, String errorMessageTemplate,
			Object... errorMessageArgs) {
		if (map == null) {
			return;
		}
		if (map.size() < minInclusive || map.size() > maxInclusive) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}

	public static void size(Object[] array, int minInclusive, int maxInclusive, String errorMessageTemplate,
			Object... errorMessageArgs) {
		if (array == null) {
			return;
		}
		if (array.length < minInclusive || array.length > maxInclusive) {
			throw new DragonException(88888,format(errorMessageTemplate, errorMessageArgs));
		}
	}

	private static String format(String template, Object... args) {
		if (template != null && args != null && args.length > 0) {
			return String.format(template, args);
		}
		return template;
	}

	private static boolean isNonnegative(Number number) {
		// null values are valid
		if (number == null) {
			return true;
		}
		if (number instanceof Integer) {
			return number.intValue() >= 0;
		} else if (number instanceof Long) {
			return number.longValue() >= 0;
		} else if (number instanceof Double) {
			return number.doubleValue() >= 0;
		} else if (number instanceof Float) {
			return number.floatValue() >= 0;
		} else if (number instanceof BigInteger) {
			return ((BigInteger) number).compareTo(BigInteger.ZERO) >= 0;
		} else if (number instanceof BigDecimal) {
			return ((BigDecimal) number).compareTo(BigDecimal.ZERO) >= 0;
		} else {
			return number.intValue() >= 0;
		}
	}

	private static boolean isPositive(Number number) {
		// null values are valid
		if (number == null) {
			return true;
		}
		if (number instanceof Integer) {
			return number.intValue() > 0;
		} else if (number instanceof Long) {
			return number.longValue() > 0;
		} else if (number instanceof Double) {
			return number.doubleValue() > 0;
		} else if (number instanceof Float) {
			return number.floatValue() > 0;
		} else if (number instanceof BigInteger) {
			return ((BigInteger) number).compareTo(BigInteger.ZERO) > 0;
		} else if (number instanceof BigDecimal) {
			return ((BigDecimal) number).compareTo(BigDecimal.ZERO) > 0;
		} else {
			return number.intValue() > 0;
		}
	}

	private static boolean isValidDigits(Number num, int integer, int fraction) {
		// null values are valid
		if (num == null) {
			return true;
		}

		BigDecimal bigNum;
		if (num instanceof BigDecimal) {
			bigNum = (BigDecimal) num;
		} else {
			bigNum = new BigDecimal(num.toString()).stripTrailingZeros();
		}

		int integerPartLength = bigNum.precision() - bigNum.scale();
		int fractionPartLength = bigNum.scale() < 0 ? 0 : bigNum.scale();

		return (integer >= integerPartLength && fraction >= fractionPartLength);
	}

	private static boolean isValidDigits(CharSequence num, int integer, int fraction) {
		// null values are valid
		if (num == null) {
			return true;
		}

		BigDecimal bigNum;
		try {
			bigNum = new BigDecimal(num.toString());
		} catch (NumberFormatException e) {
			// invalid number
			return false;
		}

		int integerPartLength = bigNum.precision() - bigNum.scale();
		int fractionPartLength = bigNum.scale() < 0 ? 0 : bigNum.scale();

		return (integer >= integerPartLength && fraction >= fractionPartLength);
	}

	static class EmailValidator {
		private static final String ATOM = "[a-z0-9!#$%&'*+/=?^_`{|}~-]";
		private static final String DOMAIN = ATOM + "+(\\." + ATOM + "+)*";
		private static final String IP_DOMAIN = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\]";
		private static final int MAX_LOCAL_PART_LENGTH = 64;
		private static final int MAX_DOMAIN_PART_LENGTH = 255;
		
		private static final int PARTS_LENGTH = 2;
		private static final String POINT = ".";
		
		/**
		 * Regular expression for the local part of an email address (everything before
		 * '@')
		 */
		private final Pattern localPattern = Pattern.compile(ATOM + "+(\\." + ATOM + "+)*",
				CASE_INSENSITIVE);

		/**
		 * Regular expression for the domain part of an email address (everything after
		 * '@')
		 */
		private final Pattern domainPattern = Pattern.compile(DOMAIN + "|" + IP_DOMAIN,
				CASE_INSENSITIVE);

		public boolean isValid(CharSequence value) {
			if (value == null || value.length() == 0) {
				return true;
			}

			// note a split limit of 3 is used as it causes all characters following to an
			// (illegal) second @ character to
			// be put into a separate array element, avoiding the regex application in this
			// case since the resulting array
			// has more than 2 elements
			String[] emailParts = value.toString().split("@", 3);
			if (emailParts.length != PARTS_LENGTH) {
				return false;
			}

			// if we have a trailing dot in local or domain part we have an invalid email
			// address.
			// the regular expression match would take care of this, but IDN.toASCII drops
			// the trailing '.'
			// (imo a bug in the implementation)
			if (emailParts[0].endsWith(POINT) || emailParts[1].endsWith(POINT)) {
				return false;
			}

			if (!matchPart(emailParts[0], localPattern, MAX_LOCAL_PART_LENGTH)) {
				return false;
			}

			return matchPart(emailParts[1], domainPattern, MAX_DOMAIN_PART_LENGTH);
		}

		private boolean matchPart(String part, Pattern pattern, int maxLength) {
			String asciiString;
			try {
				asciiString = toAscii(part);
			} catch (DragonException e) {
				return false;
			}

			if (asciiString.length() > maxLength) {
				return false;
			}

			Matcher matcher = pattern.matcher(asciiString);
			return matcher.matches();
		}

		private String toAscii(String unicodeString)  {
			StringBuilder asciiString = new StringBuilder();
			int start = 0;
			int end = unicodeString.length() <= 63 ? unicodeString.length() : 63;
			while (true) {
				// IDN.toASCII only supports a max "label" length of 63 characters. Need to
				// chunk the input in these sizes
				asciiString.append(IDN.toASCII(unicodeString.substring(start, end)));
				if (end == unicodeString.length()) {
					break;
				}
				start = end;
				end = start + 63 > unicodeString.length() ? unicodeString.length() : start + 63;
			}

			return asciiString.toString();
		}
	}

}
